/*
 * Copyright (C) 2006 Roman Krashanitsa
 *
 * @author Roman Krashanitsa
 *
 * This file is part of paparazzi.
 *
 * paparazzi is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * paparazzi is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with paparazzi; see the file COPYING.  If not, write to
 * the Free Software Foundation, 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA. 
 *
 */

package pprz.components;

import java.awt.Point;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Point2D;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;

import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.text.Position;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import net.jscience.math.MathFP;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
import org.xml.sax.helpers.DefaultHandler;

import pprz.data_components.Aircraft;
import pprz.data_components.DownlinkMessageEvent;
import pprz.data_components.DownlinkMessageEventListener;
import pprz.data_components.GeographicConverter;
import pprz.data_components.Message;
import pprz.data_components.Tile;

public class FlightPlan extends JScrollPane 
implements DownlinkMessageEventListener, ActionListener {

	private static final long serialVersionUID = 1L;
	private JTree tree;
	private HashMap<Integer,JTree> acFlightPlans;
	private HashMap<Integer,Aircraft> acInfo=null;  //there is only one  //  @jve:decl-index=0:
	private Map map;
	private Point2D.Double home;
	private Point2D.Double loc;
	private HashMap<String,Point2D.Double> waypoints;
//	private Link link;
//	private int cur_block, cur_stage;
	
	public static class ITag {//implements IconAndTipCarrier {
		private String name;

		private String data;

		private Attributes attr;

		private Icon icon;

		private String tipText;

		public ITag(String n, Attributes a) {
			name = n;
			//attr = a;
			attr=new AttributesImpl(a);
			for (int i = 0; i < attr.getLength(); i++) {
				String aname = attr.getQName(i);
				String value = attr.getValue(i);
				if (aname.equals("icon")) {
					tipText = value;
					icon = new ImageIcon(value);
					break;
				}
			}
		}

		public String getName() {
			return name;
		}

		public Attributes getAttributes() {
			return attr;
		}

		public void setData(String d) {
			data = d;
		}

		public String getData() {
			return data;
		}

		public String getToolTipText() {
			return tipText;
		}

		public Icon getIcon() {
			return icon;
		}

		public void addData(String d) {
			if (data == null) {
				setData(d);
			} else {
				data += d;
			}
		}

		public String getAttributesAsString() {
			StringBuffer buf = new StringBuffer(256);
			for (int i = 0; i < attr.getLength(); i++) {
				buf.append(attr.getQName(i));
				buf.append("=\"");
				buf.append(attr.getValue(i));
				buf.append("\" ");
			}
			return buf.toString();
		}

		public String toString() {
			String a = getAttributesAsString();
			return name + ": " + a + (data == null || "".equals(data) ? "" : " (" + data + ")");
		}
	} //class ITag
	
	
	public static class XMLIconTreeHandler extends DefaultHandler {
		private DefaultMutableTreeNode root, currentNode;

		public DefaultMutableTreeNode getRoot() {
			return root;
		}

		// SAX Parser Handler methods...
		public void startElement(String namespaceURI, String lName, // local name
				String qName, // qualified name
				Attributes attrs) throws SAXException {
			String eName = lName; // element name
			if ("".equals(eName))
				eName = qName;
			ITag t = new ITag(eName, attrs);
			DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(t);
			if (currentNode == null) {
				root = newNode;
			} else {
				// must not be the root node...
				currentNode.add(newNode);
			}
			currentNode = newNode;
		}

		public void endElement(String namespaceURI, String sName, // simple name
				String qName // qualified name
		) throws SAXException {
			currentNode = (DefaultMutableTreeNode) currentNode.getParent();
		}

		public void characters(char buf[], int offset, int len)
				throws SAXException {
			String s = new String(buf, offset, len).trim();
			((ITag) currentNode.getUserObject()).addData(s);
		}
	}

	private int ac; //selected aircraft (set by parent or as a result of action)

	public TreeModel parse(String filename) {
		SAXParserFactory factory = SAXParserFactory.newInstance();
		XMLIconTreeHandler handler = new XMLIconTreeHandler();
		try {
			// Parse the input
			SAXParser saxParser = factory.newSAXParser();
			saxParser.parse(new File(filename), handler);
		} catch (Exception e) {
			System.err.println("File Read Error: " + filename);
			e.printStackTrace();
			return new DefaultTreeModel(new DefaultMutableTreeNode("error"));
		}
		return new DefaultTreeModel(handler.getRoot());
	}

	public FlightPlan(HashMap<Integer,Aircraft> acInfo) throws IOException {
		super();
		this.acInfo=acInfo;
		acFlightPlans=new HashMap<Integer,JTree> ();
		home=new Point2D.Double();
		loc=new Point2D.Double();
		waypoints=new HashMap<String,Point2D.Double>();

	}

	public void parseXML(String fileName) {
		tree = new JTree(parse(fileName));
		tree.setLargeModel(true);
		setViewportView(tree);		
	}

	private void check_add(int ac)
	{
		if (!(acFlightPlans.containsKey(ac)))
		{
			Aircraft a=acInfo.get(ac);
			String s=a.get_flight_plan();
			if (s==null) return;
			TreeModel t=parse(s);
			acFlightPlans.put(ac, new JTree(t));
		}
	}
	public void selectAC(int ac)
	{
		this.ac=ac;
		check_add(ac);
		tree=acFlightPlans.get(ac);
		DefaultMutableTreeNode root=(DefaultMutableTreeNode)tree.getModel().getRoot();
	
		Attributes n=((ITag)root.getUserObject()).getAttributes();
		loc.setLocation(Double.valueOf(n.getValue(n.getIndex("lat0"))),
						 Double.valueOf(n.getValue(n.getIndex("lon0"))));
		root=(DefaultMutableTreeNode)tree.getNextMatch("waypoints", 0, Position.Bias.Forward).getLastPathComponent();
		Point pp=new Point();
		String s; 
		for (Enumeration e = root.children() ; e.hasMoreElements() ;) {
			root=(DefaultMutableTreeNode)e.nextElement();
			n=((ITag)root.getUserObject()).getAttributes();
			Point2D.Double p=new Point2D.Double();
			p.setLocation(Double.valueOf(n.getValue(n.getIndex("x"))),
					 Double.valueOf(n.getValue(n.getIndex("y"))));
			
			GeographicConverter.UTM utm=GeographicConverter.LLtoUTM(23,loc.x,loc.y);
			utm.easting+=p.x;
			utm.northing+=p.y;
			
			p.setLocation(utm.easting,utm.northing);
			s=n.getValue(n.getIndex("name"));
			waypoints.put(s, p);
			if (s.compareToIgnoreCase("HOME")==0) home.setLocation(p);
	     }
		setViewportView(tree);
		if (map!=null) //ask map to display FP info
		{
			map.setLoc(loc);
			map.centerMap();
			Point center=Tile.LL2Pix(loc.x, loc.y,0);
			GeographicConverter.UTM utm=GeographicConverter.LLtoUTM(23,loc.x,loc.y);
			map.removeMapMark(0);
//			map.addMapMark(0,(Shape)new Ellipse2D.Double(-0.5,-0.5,1,1), utm, new Point2D.Double(10,10));
			Collection<Point2D.Double> c=waypoints.values();
			int i=1000;
			for (Point2D.Double w : c) //add waypoint symbols on the map
			{
//				map.addMapMark(i++,new Area(new Ellipse2D.Double(-0.5,-0.5,1,1)), new Point(pp.x,pp.y),new Point2D.Double(10,10));
			}
			map.redrawMapMarks();
			map.redrawBackground();
		}
	}
	
	
	// set current block and stage in the flight plan
	public void set(int ac_id, int block, int stage)
	{
		JTree set_tree=(ac_id==this.ac)?tree:acFlightPlans.get(ac_id);
		if (set_tree==null) return;
		TreePath t=set_tree.getNextMatch("blocks",0,Position.Bias.Forward);
		TreeNode n=(TreeNode)t.getLastPathComponent();
		if (block>n.getChildCount()-1) return;
		t=t.pathByAddingChild(n.getChildAt(block));
		n=(TreeNode)t.getLastPathComponent();
		boolean first=false;
		int i=0;
		while (i<n.getChildCount() && !first) //find first stage in the block
		{
			if (!n.getChildAt(i).toString().contains("exception")) 
				first=true;
			else i++;
		}
		if (stage+i>n.getChildCount()-1) return;
		t=t.pathByAddingChild(n.getChildAt(stage+i));
		set_tree.setSelectionPath(t);
		
		if (map!=null) //ask map to display info from the flight plan
		{
			DefaultMutableTreeNode node=(DefaultMutableTreeNode)t.getLastPathComponent();
			ITag tag=((ITag)node.getUserObject());
			Attributes a=tag.getAttributes();
			if (tag.name.compareToIgnoreCase("circle")==0)
			{
				String wpName=a.getValue("wp");
				Double radius=Double.parseDouble(a.getValue("radius"));
				Point2D.Double wp=waypoints.get(wpName);
				if (wp!=null) {
					Point p1=Tile.UTM2Pix(wp.y-radius, wp.x-radius, 0);
					Point p2=Tile.UTM2Pix(wp.y+radius, wp.x+radius, 0);
					Point pc=Tile.UTM2Pix(wp.y, wp.x, 0);
					map.removeMapMark(2000);
//					map.addMapMark(2000,new Ellipse2D.Double(-0.5,-0.5,1,1), new Point(pc.x,pc.y),new Point2D.Double(p2.x-p1.x,p1.y-p2.y));
				}
			}
			map.redrawMapMarks();
			map.redrawBackground();
			
		}
		
		
		
	}
	public void messageEvent(DownlinkMessageEvent ev) {
		Message m = ev.getMessage();
		int ac=m.acRec().get_id();
		//process only downlink messages id=10 from currect AC
		if (m.msgClass()!=0 || m.id()!=10 ) return;
		assert(ac!=0);
		check_add(ac);
		set(m.acRec().get_id(),(int)MathFP.toLong(m.getFields().get(0)[0]), //block
			(int)MathFP.toLong(m.getFields().get(1)[0])); //stage
	}

	public void actionPerformed(ActionEvent arg0) {
		if (arg0.getSource().getClass() == AcInfoContainer.class)
			selectAC(((AcInfoContainer) arg0.getSource()).getSelected());
	}
	
	public void linkMap(Map map)
	{
		this.map=map;
	}
	
}
